package com.hyt.it.ogt.kq.service.gov.schedule.impl;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.hyt.it.ogt.kq.service.gov.schedule.impl.ExamRoomScheduleImpl.PackageExamRoomCandidate;
import com.hyt.it.ogt.kq.service.gov.schedule.impl.support.CandidateTotegherFactory;
import com.hyt.it.ogt.kq.service.gov.schedule.model.Candidate;
import com.hyt.it.ogt.kq.service.gov.schedule.model.ExamInfo;
import com.hyt.it.ogt.kq.service.gov.schedule.model.ExamRoom;
import com.hyt.it.ogt.kq.service.gov.schedule.model.ScheduleConstrain;
import com.hyt.it.ogt.kq.service.gov.schedule.strategy.ScheduleHelper;
import com.hyt.it.ogt.kq.service.gov.schedule.utils.BitSetUtils;
import com.hyt.it.ogt.kq.service.gov.schedule.utils.GroupUtils;
import com.hyt.it.ogt.kq.service.gov.schedule.utils.ScheduleUtils;
import com.hyt.it.ogt.kq.service.gov.schedule.utils.SimilarUtils;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import lombok.Data;
import lombok.Getter;

/**
 * 考场考生分组， 针对单任务多批次的编排进行考场和考生的分组
 * 并根据考生的分组情况对考生设置考生编排ID号
 */
@Getter
public class ScheduleGroupHandler {
    private static final Logger logger = LoggerFactory.getLogger(ScheduleGroupHandler.class);

    private int totalCandidateNumber = -1;

    // 考生分组MAP: Map<String(批次号), Map<String(分组名), CandidateGroup(考生分组)
    // private Map<String, Map<String, CandidateGroup>> candidateGroupMap;

    // 进行调整后的按批次，考生组队列，List中按顺序对应其它批次适合在一起的考生组
    private Map<String, List<String>> adjustCandidateGroupList;

    private List<String> batchNoList;

    private Set<String> cacheFilterIndexs;

    private ScheduleConstrain scheduleConstrain;

    // 各批次中最大考生分组数
    private int maxCandidateGroupNumber = -1;

    public ScheduleGroupHandler() {
        // this.candidateGroupMap = new HashMap<>();
        this.adjustCandidateGroupList = new HashMap<>();
        this.batchNoList = new ArrayList<>();
        this.cacheFilterIndexs = new HashSet<>();

    }

    public void clear() {
        this.totalCandidateNumber = -1;
        // this.candidateGroupMap.clear();
        this.adjustCandidateGroupList.clear();
        this.batchNoList.clear();
        this.cacheFilterIndexs.clear();
    }

    public void scheduleGroup(ScheduleConstrain scheduleConstrain, List<ExamRoom> examRooms,
            List<ExamInfo> examInfos) {

        this.scheduleConstrain = scheduleConstrain;
        // 对考场进行分组
        List<String> groupAttrs = new ArrayList<>(scheduleConstrain.getGroupAttrs());

        examInfos.stream().forEach(item -> {
            this.batchNoList.add(item.getBatchId());
        });

        // 在分组信息里面增加对应的分组参数应答方式
        groupAttrs.add(0, "examType");

        if (ObjectUtils.isNotNull(examRooms)) {
            Map<String, List<ExamRoom>> examRoomGroupMap = GroupUtils.groupExamRoomsByAttrs(examRooms, groupAttrs);
            ScheduleHelper.getInstance().setExamRoomGroupMap(examRoomGroupMap);
        }

        // 对参与编排的考生在编排环境进行初始化
        logger.debug("start init schedule candidate size {}", examInfos.size());
        this.initScheduleCandidate(examInfos);

        // 对考生进行分组
        logger.debug("start group examinfo candidate ");
        this.groupExamInfoCandidate(scheduleConstrain, examInfos, groupAttrs);

        // 将考生和考场进行分组打包
        logger.debug("start package candidate and examroom");
        this.packageCandidateExamRooms(scheduleConstrain,
                ScheduleHelper.getInstance().getExamRoomGroupMap(),
                ScheduleHelper.getInstance().getCandidateGroupMap());

        // 生成考生的编排虚拟考生号
        logger.debug("start create candidate vir id");
        CandidateTotegherFactory.getHandler(scheduleConstrain)
                .togetherCandidate(ScheduleHelper.getInstance().getBatchPackageGroupMap());

        // 缓存虚拟考生对应的考生科目
        logger.debug("start cache candidate subject");
        this.cacheVirCandidateSubjects(examInfos);

    }

    /****
     * 将批次考生按编排属性进行分组
     * 
     * @param scheduleConstrain
     * @param examInfos
     * @param groupAttrs
     */
    public void groupExamInfoCandidate(ScheduleConstrain scheduleConstrain, List<ExamInfo> examInfos,
            List<String> groupAttrs) {
        if (this.totalCandidateNumber == -1) {
            this.initScheduleCandidate(examInfos);
        }
        ScheduleHelper scheduleHelper = ScheduleHelper.getInstance();
        Map<String, Map<String, List<Candidate>>> totalBatchCandidateGroupMap = new HashMap<>();
        Set<String> totalCandidateGroupName = new HashSet<>();
        Map<String, Map<String, Candidate>> totalExamCandidateMap = ScheduleUtils.getExamCandidateMap(examInfos);
        // 对考场考生进行分组
        for (int index = 0; index < examInfos.size(); index++) {
            ExamInfo batchExamInfo = examInfos.get(index);
            String batchNo = batchExamInfo.getBatchId();
            List<Candidate> batchCandidates = batchExamInfo.getCandidates();
            // this.candidateGroupMap.put(batchNo, new HashMap<>());
            Map<String, List<Candidate>> batchCandidateGroupMap = null;

            // 如为单科目，则在组合名中增加相应的科目名称
            if (scheduleConstrain.isSingleSubject()) {
                if (scheduleConstrain.isMainTainPosition()) {
                    batchCandidateGroupMap = GroupUtils.groupNeedMaintainPositionCandidateByAttrs(batchCandidates,
                            groupAttrs, totalExamCandidateMap, ScheduleHelper.getInstance()
                                    .getCandidateBatchFeatureMap());
                } else {
                    List<String> copyGroupsAttrs = new ArrayList<>();
                    CollectionUtils.addAll(copyGroupsAttrs, groupAttrs.toArray());
                    copyGroupsAttrs.add("subject");
                    batchCandidateGroupMap = GroupUtils.groupCandidateByAttrs(batchCandidates,
                            groupAttrs);
                }

            } else {
                batchCandidateGroupMap = GroupUtils.groupCandidateByAttrs(batchCandidates,
                        groupAttrs);
            }

            totalCandidateGroupName.addAll(batchCandidateGroupMap.keySet());
            totalBatchCandidateGroupMap.put(batchNo, batchCandidateGroupMap);
        }

        // 后面可再加上组考生数量比较，取组合后的组数量最大的进行合并
        totalCandidateGroupName = GroupUtils.mergeGroupName(totalCandidateGroupName);
        for (Map.Entry<String, Map<String, List<Candidate>>> batchCandidateGroupMap : totalBatchCandidateGroupMap
                .entrySet()) {
            String batchNo = batchCandidateGroupMap.getKey();
            ScheduleHelper.getInstance().getCandidateGroupMap().put(batchNo, new HashMap<>());
            for (Map.Entry<String, List<Candidate>> groupCandidateMap : batchCandidateGroupMap.getValue().entrySet()) {
                String currGroupName = groupCandidateMap.getKey();
                ImmutablePair<String, String> findResult = SimilarUtils.findCanReplaceKey(currGroupName,
                        new ArrayList<>(totalCandidateGroupName));
                String groupName = findResult.getLeft();
                String mergeGroupName = findResult.getRight();

                if (!groupName.equalsIgnoreCase(mergeGroupName)) {
                    totalCandidateGroupName.add(mergeGroupName);
                    groupName = mergeGroupName;
                }

                if (ScheduleHelper.getInstance().getCandidateGroupMap().get(batchNo)
                        .containsKey(groupName)) {
                    ScheduleHelper.getInstance().getCandidateGroupMap().get(batchNo)
                            .get(groupName).addAll(groupCandidateMap.getValue());
                } else {
                    ScheduleHelper.getInstance().getCandidateGroupMap().get(batchNo).put(
                            groupName,
                            groupCandidateMap.getValue());

                }

            }
        }

        logger.info("total candidate group name size {}", totalCandidateGroupName.size());

    }

    /***
     * 将考生和考场进行对应
     * 
     * @param examRoomGroupMap
     * @param candidateGroupMap
     */
    public void packageCandidateExamRooms(ScheduleConstrain scheduleConstrain,
            Map<String, List<ExamRoom>> examRoomGroupMap,
            Map<String, Map<String, List<Candidate>>> candidateGroupMap) {
        for (Map.Entry<String, Map<String, List<Candidate>>> item : candidateGroupMap.entrySet()) {
            String batchNo = item.getKey();
            Map<String, List<Candidate>> groupCandidateMap = item.getValue();
            Map<String, PackageExamRoomCandidate> packageGroupMap = this.packageCandidateExamRoom(scheduleConstrain,
                    examRoomGroupMap,
                    groupCandidateMap);
            ScheduleHelper.getInstance().getBatchPackageGroupMap().put(batchNo, packageGroupMap);
        }
    }

    /**
     * 将考生和考场进行组包
     */
    private Map<String, PackageExamRoomCandidate> packageCandidateExamRoom(ScheduleConstrain scheduleConstrain,
            Map<String, List<ExamRoom>> examRoomGroup,
            Map<String, List<Candidate>> candidateGroup) {
        Map<String, PackageExamRoomCandidate> packedCandidateExamRoomMap = new HashMap<>();
        Map<String, Set<String>> examRoomGroupIncludecandidateGroupMap = ScheduleHelper.getInstance()
                .getExamRoomGroupIncludecandidateGroupMap();
        List<String> examRoomGroupNames = examRoomGroup.entrySet().stream().map(Map.Entry::getKey)
                .collect(Collectors.toList());

        if (examRoomGroupNames.size() == 0) {
            return packedCandidateExamRoomMap;
        }

        // 匹配的最大阀值
        int threshold = examRoomGroupNames.get(0).split("\\|").length;

        examRoomGroup.entrySet().forEach(item -> {
            packedCandidateExamRoomMap.put(item.getKey(), new PackageExamRoomCandidate(item.getKey(), item.getValue()));
        });

        for (Map.Entry<String, List<Candidate>> item : candidateGroup.entrySet()) {
            String groupName = item.getKey();
            String bestExamRoomGroupName = SimilarUtils.findSimlarityByOrder(groupName, examRoomGroupNames, threshold);

            logger.info("candidate {}  examroom {}  best {}", groupName, examRoomGroupNames, bestExamRoomGroupName);
            if (Objects.nonNull(bestExamRoomGroupName)) {
                packedCandidateExamRoomMap.get(bestExamRoomGroupName).candidates.add(item.getValue());
                if (!examRoomGroupIncludecandidateGroupMap.containsKey(bestExamRoomGroupName)) {
                    examRoomGroupIncludecandidateGroupMap.put(bestExamRoomGroupName, new HashSet<>());
                }
                examRoomGroupIncludecandidateGroupMap.get(bestExamRoomGroupName).add(groupName);
                if (!ScheduleHelper.getInstance().getCandidateGroup2ExamRoomGroup().containsKey(groupName)) {
                    ScheduleHelper.getInstance().getCandidateGroup2ExamRoomGroup().put(groupName,
                            bestExamRoomGroupName);
                }
            } else {
                logger.info("can't find match exam room group name !");
            }
        }
        return packedCandidateExamRoomMap;
    }

    /**
     * 对参与编排的考生在编排环境进行初始化
     */
    private void initScheduleCandidate(List<ExamInfo> examInfos) {
        Set<Candidate> candidateSet = new HashSet<>();
        examInfos.forEach(item -> {
            candidateSet.addAll(item.getCandidates());
            item.getCandidates().forEach(candidate -> {
                if (!ScheduleHelper.getInstance().getCandidateSujectsMap().containsKey(candidate.getBmStudentId())) {
                    ScheduleHelper.getInstance().getCandidateSujectsMap().put(candidate.getBmStudentId(),
                            new HashSet<>());
                }
                ScheduleHelper.getInstance().getCandidateSujectsMap().get(candidate.getBmStudentId())
                        .add(candidate.getSubject());
            });
        });
        this.totalCandidateNumber = candidateSet.size();
        logger.info("需编排的总学生数 : {}", this.totalCandidateNumber);
        List<Candidate> candidateList = new ArrayList<>(candidateSet);
        IntStream.range(0, this.totalCandidateNumber).forEach(index -> {
            // 设置考生在编排环境序列号，用于生成BitSet，用于对考生组进行特征比较
            ScheduleHelper.getInstance().getCandidate2IndexMap().put(candidateList.get(index).getBmStudentId(), index);
            // 初始化考生的批次BitSet，用于组合跨批次考生的组合
            ScheduleHelper.getInstance().getCandidateBatchFeatureMap().put(candidateList.get(index).getBmStudentId(),
                    new BitSet(examInfos.size()));
        });

        Map<String, ExamInfo> examinfoMap = examInfos.stream()
                .collect(Collectors.toMap(ExamInfo::getBatchId, item -> item));
        List<String> examBatchNos = examInfos.stream().map(ExamInfo::getBatchId).collect(Collectors.toList());
        Collections.sort(examBatchNos);
        IntStream.range(0, examBatchNos.size()).forEach(index -> {
            String batchNO = examBatchNos.get(index);
            examinfoMap.get(batchNO).getCandidates().stream().forEach(item -> {
                ScheduleHelper.getInstance().getCandidateBatchFeatureMap().get(item.getBmStudentId()).set(index);
            });
        });
        logger.debug("考生 批次 BitSet特征共有 :{}", ScheduleHelper.getInstance().getCandidateBatchFeatureMap().size());
    }

    /***
     * 缓存虚拟考生对应批次的考试科目
     * 
     * @param examInfos
     */
    private void cacheVirCandidateSubjects(List<ExamInfo> examInfos) {
        ScheduleHelper scheduleHelper = ScheduleHelper.getInstance();
        Map<String, Map<String, Candidate>> examCandidateMap = ScheduleUtils.getExamCandidateMap(examInfos);
        Map<String, Map<String, String>> virId2CandidateIdMap = scheduleHelper.getVirId2CandidateIdMap();
        Map<String, List<String>> virId2ExamSubjectsMap = scheduleHelper.getVirId2ExamSubjectsMap();
        for (Map.Entry<String, Map<String, String>> entry : virId2CandidateIdMap.entrySet()) {
            String virCandidateId = entry.getKey();
            if (!virId2ExamSubjectsMap.containsKey(virCandidateId)) {
                virId2ExamSubjectsMap.put(virCandidateId, new ArrayList<>());
            }
            Map<String, String> batchCandidateIdMap = entry.getValue();

            for (String batchNO : this.batchNoList) {
                String candidateID = batchCandidateIdMap.get(batchNO);
                if (candidateID != null) {
                    Candidate candidate = examCandidateMap.get(batchNO).get(candidateID);
                    if (candidate == null) {
                        throw new RuntimeException("exam candidate not found for vir candidate id " + virCandidateId
                                + " and batch no " + batchNO);
                    } else {
                        virId2ExamSubjectsMap.get(virCandidateId).add(candidate.getSubject());
                    }

                } else {
                    virId2ExamSubjectsMap.get(virCandidateId).add("ANY");
                }
            }
        }
    }

    @Data
    public static class ExamRoomGroup {
        public String groupName;
        public List<ExamRoom> examRooms;
        public int examRoomNumber;

        public ExamRoomGroup(String groupName, List<ExamRoom> examRooms) {
            this.groupName = groupName;
            this.examRooms = examRooms;
            this.examRoomNumber = this.examRooms.size();
        }
    }

}
